﻿using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;

// ReSharper disable InconsistentlySynchronizedField

namespace Hsenl.Network {
    public sealed class KcpServer : Acceptor {
        private const int ThreadCount = 12;
        private const int extraSocketCount = 1;

        private Socket _socket;
        private readonly List<SocketWrap> _extraSockets = new();
        private readonly UdpCollecter _acceptor = new();
        private readonly ConcurrentDictionary<long, KcpChannel> _channels = new();
        private readonly NetworkPool<KcpChannel> _channelPool = new();
        private readonly NetworkPool<UdpIOCPReceiver> _receiverPool = new();
        private readonly NetworkPool<UdpIOCPSender> _senderPool = new();

        public Configure Config { get; private set; }
        public event Action<long> OnConnectedEvent;
        public event Action<long, int> OnDisconnectedEvent;
        public Container Container { get; private set; }

        private readonly ConcurrentQueue<(byte[] data, int offset, int count, EndPoint remoteEndPoint)> _sendQueue = new();
        private readonly object _createChannelLocker = new();
        private readonly object _dislocker = new();

        public KcpServer(Configure config) {
            this.Config = config ?? throw new ArgumentNullException(nameof(config));

            this.Container = new Container();
            this.Container.Let<KcpChannel>().AllowAutoInjection();
            this.Container.Let<KcpPacketReceiverSender>().As<IKcpHandler>().ShareInjection();
            this.Container.Let<KcpPacketReceiverSender>().As<IPacketReceiver>().ShareInjection();
            this.Container.Let<KcpPacketReceiverSender>().As<IPacketSender>().ShareInjection();
            this.Container.Let<KcpPacketReceiverSender>().As<IMessageReader>().ShareInjection();
            this.Container.Let<KcpPacketReceiverSender>().As<IMessageWriter>().ShareInjection();
        }

        public override void StartAccept() {
            this.CheckDisposedException();

            if (this._socket != null) {
                throw new InvalidOperationException("Kcp Server Already started.");
            }

            var localEndPoint = this.Config.GetLocalIPEndPoint();
            if (localEndPoint == null)
                throw new Exception($"Listen IPEndPoint Invalid!");

            this._socket = new Socket(localEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
            this._socket.ReceiveBufferSize = this.Config.RecvBufferSize;
            this._socket.SendBufferSize = this.Config.SendBufferSize;
            this._socket.Bind(localEndPoint);
#if NET45_OR_GREATER
            const int SIO_UDP_CONNRESET = -1744830452;
            this._socket.IOControl(SIO_UDP_CONNRESET, new byte[] { 0, 0, 0, 0 }, null);
#else
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
                const int SIO_UDP_CONNRESET = -1744830452;
                this._socket.IOControl(SIO_UDP_CONNRESET, new byte[] { 0, 0, 0, 0 }, null);
            }
#endif

            if (extraSocketCount > 0) {
                var port = localEndPoint.Port + 1;
                for (var i = 0; i < extraSocketCount; i++) {
                    var socket = new Socket(localEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
                    socket.ReceiveBufferSize = this.Config.RecvBufferSize;
                    socket.SendBufferSize = this.Config.SendBufferSize;
                    var ep = new IPEndPoint(localEndPoint.Address, port + i);
                    socket.Bind(ep);

                    this._extraSockets.Add(new SocketWrap() { Socket = socket, ConnentCount = 0 });

#if NET45_OR_GREATER
                    const int SIO_UDP_CONNRESET = -1744830452;
                    socket.IOControl(SIO_UDP_CONNRESET, new byte[] { 0 }, null);
#else
                    if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
                        const int SIO_UDP_CONNRESET = -1744830452;
                        socket.IOControl(SIO_UDP_CONNRESET, new byte[] { 0 }, null);
                    }
#endif
                }
            }

            UdpIOCPReceiver receiver;
#if UNITY_5_3_OR_NEWER
            receiver = new UdpIOCPReceiver(this._socket, SocketBufferKind.Array);
#else
            receiver = new UdpIOCPReceiver(this._socket, SocketBufferKind.Memory);
#endif
            this._acceptor.AddReceiver(receiver);

            var recvBuffer = new HBuffer();

#if UNITY_5_3_OR_NEWER
            receiver.RecvBufferGetterArray += len => {
                if (recvBuffer.Capacity < len)
                    recvBuffer.Capacity = len;
                return (recvBuffer.GetBuffer(), 0, len);
            };
#else
            receiver.RecvBufferGetter += len => recvBuffer.GetMemory(len);
#endif
            receiver.OnRecvFromDataEvent += this.OnRecvFromData;

            UdpIOCPSender sender;
#if UNITY_5_3_OR_NEWER
            sender = new UdpIOCPSender(this._socket, SocketBufferKind.Array);
#else
            sender = new UdpIOCPSender(this._socket, SocketBufferKind.Memory);
#endif
            this._acceptor.AddSender(sender);

#if UNITY_5_3_OR_NEWER
            sender.SendBufferGetterArray += len => {
                if (this._sendQueue.TryDequeue(out var tuple)) {
                    return tuple;
                }

                return default;
            };
#else
            sender.SendBufferGetter += len => {
                if (this._sendQueue.TryDequeue(out var tuple)) {
                    return (tuple.data.AsMemory(tuple.offset, tuple.count), tuple.remoteEndPoint);
                }

                return default;
            };
#endif
            sender.OnSendToDataEvent += this.OnSendToData;

            this._acceptor.ReceiveFromAsyncAll();

            for (var i = 0; i < ThreadCount; i++) {
                var num = i;
                new Thread(() => { _ = this.KcpDriver(num); }) {
                    IsBackground = true,
                    Name = "KcpServer Worker",
                }.Start();
            }

            Log.Info($"Kcp服务器启动成功...{localEndPoint}");
        }

        private void OnRecvFromData(EndPoint remoteEndPoint, Memory<byte> data) {
            // 根据发来的数据, 判断是要连接, 还是发送消息
            var span = data.Span;
            var head = span[0];
            var remoteConv = 0u;
            
            if (head == KcpProtocalType.SYN) {
                remoteConv = IdHelper.GenerateUintId();
                var retry = 0;
                while (this._channels.ContainsKey(remoteConv)) {
                    if (++retry > 1000)
                        throw new Exception("Generate Conv Failed: Too many collisions");

                    remoteConv = IdHelper.GenerateUintId();
                }
            }

            this.OnChannelRecvData(remoteConv, data);
            switch (head) {
                case KcpProtocalType.SYN: {
                    var channel = this._channelPool.TryRent(out var result) ? result : this.CreateChannel();
                    if (channel == null)
                        throw new Exception("Create Channel Fail!");

                    var udpSocket = new UdpSocket();
                    channel.Service = this;
                    channel.UserToken = udpSocket;
                    channel.OnMessageReadedEvent += this.OnMessageReaded;
                    channel.OnMessageWritedEvent += this.OnMessageWrited;
                    channel.OnErrorEvent += this.OnChannelError;

                    // 给每个客户端都创建一个 saea, 用于发送消息
                    SocketBufferKind bufferKind;

#if UNITY_5_3_OR_NEWER
                    bufferKind = SocketBufferKind.Array;
#else
                    bufferKind = SocketBufferKind.Memory;
#endif

                    SocketWrap minsw = null;
                    var minsocket = this._socket;
                    var min = -1;
                    foreach (var socketWrap in this._extraSockets) {
                        if (min == -1) {
                            min = socketWrap.ConnentCount;
                            minsw = socketWrap;
                            minsocket = socketWrap.Socket;
                        }
                        else {
                            if (socketWrap.ConnentCount < min) {
                                min = socketWrap.ConnentCount;
                                minsw = socketWrap;
                                minsocket = socketWrap.Socket;
                            }
                        }
                    }

                    if (minsw != null)
                        minsw.ConnentCount++;

                    if (min != -1) {
                        UdpIOCPReceiver receiver;
                        if (!this._receiverPool.TryRentIOCP(out receiver, minsocket)) {
                            receiver = new UdpIOCPReceiver(minsocket, bufferKind);
                        }

                        udpSocket.AddReceiver(receiver);
                        receiver.OnClosedEvent += () => { this._receiverPool.Return(receiver); };
#if UNITY_5_3_OR_NEWER
                        receiver.RecvBufferGetterArray += len => {
                            var bytes = channel.GetRecvBuffer(len, out var offset, out var count);
                            return (bytes, offset, count);
                        };

#else
                        receiver.RecvBufferGetter += len => {
                            var memory = channel.GetRecvBuffer(len);
                            return memory;
                        };
#endif
                        receiver.OnRecvFromDataEvent += this.OnRecvFromData;
                        receiver.OnErrorEvent += channel.OnError;
                    }

                    UdpIOCPSender sender;
                    if (!this._senderPool.TryRentIOCP(out sender, minsocket)) {
                        sender = new UdpIOCPSender(minsocket, bufferKind);
                    }

                    udpSocket.AddSender(sender);
                    sender.OnClosedEvent += () => { this._senderPool.Return(sender); };
#if UNITY_5_3_OR_NEWER
                    sender.SendBufferGetterArray += len => {
                        var bytes = channel.GetSendBuffer(len, out var offset, out var count);
                        return (bytes, offset, count, channel.RemoteEndPoint);
                    };

#else
                    sender.SendBufferGetter += len => {
                        var memory = channel.GetSendBuffer(len);
                        return (memory, channel.RemoteEndPoint);
                    };
#endif

                    sender.OnSendToDataEvent += this.OnSendToData;
                    sender.OnErrorEvent += channel.OnError;

                    channel.Init(remoteConv, remoteEndPoint);
                    this._channels.TryAdd(channel.ChannelId, channel);
                    udpSocket.ReceiveFromAsync();

                    ushort newPort = 0;
                    if (min != -1) {
                        newPort = (ushort)((IPEndPoint)minsocket.LocalEndPoint).Port;
                    }

                    this.SendACKTo(remoteConv, newPort, remoteEndPoint);

                    try {
                        this.OnConnectedEvent?.Invoke(channel.ChannelId);
                    }
                    catch (Exception ex) {
                        Log.Error(ex);
                    }

                    foreach (var plug in this.ForeachPlugs<IOnChannelStarted>()) {
                        try {
                            plug.Handle(channel.ChannelId);
                        }
                        catch (Exception ex) {
                            Log.Error(ex);
                        }
                    }

                    Log.Info($"接受客户端'{remoteEndPoint}'的连接! 现在有{this._channels.Count}个客户端在连接服务器");

                    break;
                }

                case KcpProtocalType.ACK: {
                    break;
                }

                case KcpProtocalType.FIN: {
                    remoteConv = BitConverter.ToUInt32(span.Slice(1));
                    var channel = this.GetKcpChannel(remoteConv);
                    if (channel != null) {
                        lock (this._dislocker) {
                            this._channels.Remove(channel.ChannelId, out _);
                            var error = BitConverter.ToInt32(span.Slice(5));
                            this.OnChannelDisconnected(channel, error);
                        }
                    }

                    break;
                }

                case KcpProtocalType.MSG: {
                    remoteConv = BitConverter.ToUInt32(span.Slice(1));
                    var channel = this.GetKcpChannel(remoteConv);
                    if (channel == null) {
                        this.SendFINTo(remoteConv, ErrorCode.ConnectionAborted, remoteEndPoint);
                        break;
                    }

                    channel.Read(data);

                    break;
                }
            }
        }

        private void OnSendToData(EndPoint remoteEndPoint, Memory<byte> data) {
            var span = data.Span;
            var head = span[0];
            var remoteConv = BitConverter.ToUInt32(data.Slice(1).Span);
            this.OnChannelSendData(remoteConv, data);
        }

        private KcpChannel CreateChannel() {
            lock (this._createChannelLocker) {
                this.Container.StartStage();
                var channel = this.Container.Resolve<KcpChannel>();
                this.Container.EndStage();
                return channel;
            }
        }

        public override Channel GetChannel(long channelId) {
            return this.GetKcpChannel(channelId);
        }

        public KcpChannel GetKcpChannel(long channelId) {
            if (this._channels.TryGetValue(channelId, out var channel)) {
                return channel;
            }

            return null;
        }

        public override ICollection<long> GetChannelIds() {
            return this._channels.Keys;
        }

        public override void Write(long channelId, byte[] data, int offset, int count) {
            var channel = this.GetKcpChannel(channelId);
            if (channel == null) {
                return;
            }

            channel.Write(data, offset, count);
        }

        public override void Write(long channelId, Span<byte> data) {
            var channel = this.GetKcpChannel(channelId);
            if (channel == null) {
                return;
            }

            channel.Write(data);
        }

        public override void StartSend(long channelId) {
            var channel = this.GetKcpChannel(channelId);
            if (channel == null) {
                return;
            }

            ((UdpSocket)channel.UserToken).SendToAsync();
        }

        public override void OnUpdate() {
            return;
            // kcp 有了一个 update 了, 目前用不到这个
            this._acceptor.ReceiveFromAsyncAll();
            foreach (var kv in this._channels) {
                var channel = kv.Value;
                try {
                    channel.OnUpdate();
                }
                catch (Exception e) {
                    Log.Error(e);
                }
            }
        }

        private async hvoid KcpDriver(int num) {
#if NETCOREAPP3_0_OR_GREATER
            var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(16));
            while (!this.IsDisposed && await timer.WaitForNextTickAsync()) {
#else
            while (!this.IsDisposed) {
#endif
                var currentTime = TimeInfo.CurrentUtcMilliseconds;
                var eachHandleCount = this._channels.Count / ThreadCount + 1;
                var idx = 0;
                var min = num * eachHandleCount;
                var max = min + eachHandleCount;
                foreach (var kv in this._channels) {
                    if (idx >= min && idx < max) {
                        var channel = kv.Value;
                        channel.KcpUpdate(currentTime);
                    }

                    idx++;
                }

                Thread.Sleep(16);
            }
        }

        private byte[] GetSendToBytes(int len) {
            return new byte[len];
        }

        private void SendACKTo(uint remoteConv, ushort port, EndPoint remoteEndPoint) {
            var bytes = this.GetSendToBytes(7);
            bytes[0] = KcpProtocalType.ACK;
            bytes.WriteTo(1, remoteConv);
            bytes.WriteTo(5, port);
            this.WriteToSendQueue(bytes, 0, 7, remoteEndPoint);
        }

        private void SendFINTo(uint localConv, int errorId, EndPoint remoteEndPoint) {
            var bytes = this.GetSendToBytes(9);
            bytes[0] = KcpProtocalType.FIN;
            bytes.WriteTo(1, localConv);
            bytes.WriteTo(5, errorId);
            this.WriteToSendQueue(bytes, 0, 9, remoteEndPoint);
        }
        
        public void SendPingTo(uint localConv, EndPoint remoteEndPoint) {
            var bytes = this.GetSendToBytes(9);
            bytes[0] = KcpProtocalType.Ping;
            bytes.WriteTo(1, localConv);
            bytes.WriteTo(5, 123124124);
            this.WriteToSendQueue(bytes, 0, 9, remoteEndPoint);
        }

        private void WriteToSendQueue(byte[] data, int offset, int count, EndPoint remoteEndPoint) {
            this._sendQueue.Enqueue((data, offset, count, remoteEndPoint));
            this._acceptor.Sender.SendToAsync();
        }

        protected override void OnChannelError(long channelId, int errorCode) {
            try {
                base.OnChannelError(channelId, errorCode);
            }
            finally {
                if (ErrorCode.fatalSocketErrors.Contains(errorCode) || errorCode > ErrorCode.SocketErrorWatershed) {
                    this.DisconnectChannel(channelId, errorCode);
                }
            }
        }

        public override void DisconnectChannel(long channelId, int error) {
            lock (this._dislocker) {
                if (this._channels.TryRemove(channelId, out var channel)) {
                    this.SendFINTo((uint)channelId, ErrorCode.ConnectionAborted, channel.RemoteEndPoint);
                    this.OnChannelDisconnected(channel, error);
                }
            }
        }

        private void OnChannelDisconnected(KcpChannel channel, int error) {
            Log.Info($"一个客户端从服务器断开 '{channel.RemoteEndPoint}'! 现在有{this._channels?.Count ?? 0}个客户端在连接服务器");
            var channelId = 0L;
            if (channel.UserToken is UdpSocket udpSocket) {
                // kcpServer 的 socket 是长期存在的, 所以, 只关闭 EpibolySocket, 不关闭 socket
                var socket = udpSocket.Sender.Socket;
                udpSocket.Close(false);
                channelId = channel.ChannelId;
                channel.Dispose();
                foreach (var socketWrap in this._extraSockets) {
                    if (socketWrap.Socket == socket) {
                        socketWrap.ConnentCount--;
                        break;
                    }
                }

                this._channelPool.Return(channel);
            }

            foreach (var plug in this.ForeachPlugs<IOnChannelDisconnected>()) {
                try {
                    plug.Handle(channelId, error);
                }
                catch (Exception e) {
                    Log.Error(e);
                }
            }

            try {
                this.OnDisconnectedEvent?.Invoke(channelId, error);
            }
            catch (Exception e) {
                Log.Error(e);
            }
        }

        public void DisconnectAllChannels() {
            lock (this._dislocker) {
                try {
                    foreach (var kv in this._channels) {
                        var channel = kv.Value;
                        var channelId = channel.ChannelId;
                        this.SendFINTo((uint)channelId, ErrorCode.ConnectionAborted, channel.RemoteEndPoint);
                        this.OnChannelDisconnected(channel, ErrorCode.ConnectionAborted);
                    }
                }
                finally {
                    this._channels.Clear();
                }
            }
        }

        public override void Dispose() {
            if (this.IsDisposed)
                return;

            base.Dispose();

            this.DisconnectAllChannels();

            this.Config.Dispose();
            this.Config = null;
            this.Container.Dispose();
            this.Container = null;

            this._acceptor.Close(false);
            this._acceptor.Dispose();
            this._socket.Close();
            foreach (var socketWrap in this._extraSockets) {
                socketWrap.Dispose();
            }

            this._channels.Clear();
            this._channelPool.Clear();
            this._receiverPool.Clear();
            this._senderPool.Clear();
            this.OnConnectedEvent = null;
            this.OnDisconnectedEvent = null;
            this._sendQueue.Clear();
        }

        public class Configure {
            public string LocalIPHost { get; set; }
            public int Port { get; set; }
            public int Backlog { get; set; }
            public int RecvBufferSize { get; set; }
            public int SendBufferSize { get; set; }

            public IPEndPoint GetLocalIPEndPoint() {
                if (IPAddress.TryParse(this.LocalIPHost, out var address)) {
                    var endPoint = new IPEndPoint(address, this.Port);
                    return endPoint;
                }

                return null;
            }

            public void Reset() {
                this.LocalIPHost = null;
                this.Port = 0;
                this.Backlog = 0;
                this.RecvBufferSize = 0;
                this.SendBufferSize = 0;
            }

            public void Dispose() {
                this.Reset();
            }
        }

        public class SocketWrap {
            public Socket Socket { get; set; }
            public int ConnentCount { get; set; }

            public void Dispose() {
                this.Socket.Close();
                this.ConnentCount = 0;
            }
        }
    }
}